"""
@-*- coding: utf-8 -*-
@ python：python 3.8
@ 创建人员：ylx
@ 创建时间：2025/4/27 下午2:40
@ 内容：学习Python相关的基础知识
"""
"""
1.深拷贝-浅拷贝
答：深拷贝和浅拷贝是Python中对象复制的2种方式，区别在于如何复制对象的内容，他们在处理可变对象 列表/字典时有着不同的行为
首先来看浅拷贝
浅拷贝通过复制原始对象，创建一个新对象。但对于原始对象中的可变对象（如列表、字典等），它只复制了引用，而不是对象本身。
如果原始对象中的可变对象被修改，浅拷贝中的对象也会受到影响，因为它们共享相同的可变对象。
浅拷贝会创建一个新的对象，但是不会递归的复制嵌套对象，也就是浅拷贝仅仅复制对象第一层的内容
对于嵌套的对象，比如说列表中套了列表，浅拷贝。仅仅会复制引用，而不是实际的内容
import copy

# 原始列表
original_list = [1, 2, [3, 4]]

# 浅拷贝
shallow_copied_list = copy.copy(original_list)

# 修改原始列表的嵌套部分
original_list[2][0] = "modified"
original_list[0] = "hhhh"

print(original_list)        # 输出: ["hhhh", 2, ['modified', 4]]
print(shallow_copied_list)  # 输出: [1, 2, ['modified', 4]]
shallow_copied_list 是 original_list 的浅拷贝。
当我们修改 original_list 的嵌套部分 [3, 4] 时，shallow_copied_list 中的嵌套部分也会被修改
因为浅拷贝只是复制了嵌套列表的引用，而不是实际的内容。
而我们要是改变第一层的内容时，仅改变了原来的对象，复制而产生的新的对象，1层的内容还是原来对象的，不会变
浅拷贝的实现方法-
使用copy.copy()
切片操作，适用于列表


深拷贝
深拷贝通过递归地复制原始对象及其所有包含的对象，创建一个新对象
这样，即使原始对象包含可变对象，它们的副本也是独立的
如果原始对象中的可变对象被修改，深拷贝中的对象不会受到影响，因为它们拥有独立的副本。

深拷贝会递归的复制对象及其所有嵌套的对象
深拷贝不仅复制对象的第一层内容，还会复制嵌套的所有内容。
新对象与原对象完全独立，互不影响

import copy

# 原始列表
original_list = [1, 2, [3, 4]]

# 深拷贝
deep_copied_list = copy.deepcopy(original_list)

# 修改原始列表的嵌套部分
original_list[2][0] = "modified"

print(original_list)       # 输出: [1, 2, ['modified', 4]]
print(deep_copied_list)    # 输出: [1, 2, [3, 4]]
deep_copied_list 是 original_list 的深拷贝。
即使我们修改了 original_list 的嵌套部分 [3, 4]
deep_copied_list 中的内容保持不变，因为深拷贝复制了嵌套对象的实际内容。
深拷贝我们一般使用copy.deepcopy()方法

浅拷贝-只复制对象第一层内容，复制嵌套对象的引用，更快，占用内存更少，适用于对象不包含嵌套结构或者不需要独立副本的情况
深拷贝-递归的复制对象及其所有嵌套的内容，复制嵌套对象的实际内容，更慢，占用更多内存，对象包含嵌套结构且需要完全独立的副本

使用浅拷贝的场景：
当对象的内容是简单结构（如一维列表、字典等）。
当你希望保留对嵌套对象的共享引用（即修改嵌套对象会影响所有副本）。
使用深拷贝的场景：
当对象的内容是复杂结构（如嵌套列表、嵌套字典等）。
当你需要一个完全独立的副本，修改原对象不会影响副本。

测试开发过程中，如何使用深拷贝，如何使用浅拷贝
a.测试数据准备，你准备的数据可能是嵌套的字典或者列表，使用深拷贝
确保每次测试使用的都是独立的数据副本，不会因为一个测试中的影响从而影响到另一个测试
import copy

# 假设这是你的测试数据
test_data = {
    'name': 'Alice',
    'age': 30,
    'scores': [85, 90, 78]
}

def test_update_scores():
    # 使用深拷贝来创建独立的测试数据副本
    data_copy = copy.deepcopy(test_data)
    
    # 修改副本中的分数
    data_copy['scores'][0] = 100
    
    # 确保原始数据未被修改
    assert test_data['scores'][0] == 85

test_update_scores()

应用场景：

在多个测试用例之间共享相同的初始数据集，但每个测试用例都需要对数据进行不同的修改而不影响其他测试。
当测试涉及多线程并发操作时，确保每个线程操作的是独立的数据副本。
说白了，使用深拷贝的目的就是确保测试数据的独立性，如你要编写单元测试或集成测试时，准备一些基础的测试数据
，并且希望在不同的测试用例中对数据进行修改，但是这个修改又不希望影响其他测试用例的数据，可以使用深拷贝

深浅拷贝的异同
同  无论深拷贝还是浅拷贝都会创建一个新对象。即：拷贝出来的对象的id不同。
异  浅拷贝：只拷贝了对象，对象内的元素并不会发生拷贝。
    深拷贝：不仅拷贝对象，对象内的元素也会发生拷贝。


------


2.可变数据类型和不可变数据类型
Python中 可变数据类型为 列表 集合  字典
不可变数据类型为    int float str tuple bool
简单的来说就是 当变量改变的时候，数据地址是否会改变
可变数据类型 值变了 内存地址不变的数据类型
不可变数据类型 值变了，内存地址也会变，不是在原内存上修改，而是创建一个新的内存地址和值
在讲可变数据类型和不可变数据类型之前我们要讲一下引用的概念。python变量保存的是对象的引用，这个引用指向堆内存里的对象
在堆中分配的对象分为两类，一类是可变对象，一类是不可变对象。例如：s1="abc"
其实变量s1 就是对象 abc的引用，s1指向了存储abc的内存地址，如果想看s1的地址值，可以使用函数id，id会把地址值转换成十进制。使用print(id(s1))即可


------


3.静态方法/类方法
普通实例方法
定义：
普通的实例方法是绑定到类的实例上的。
第一个参数通常是 self，表示当前实例。
必须通过类的实例来调用
class MyClass:
    def instance_method(self):
        print(f"Called instance_method of {self}")

obj = MyClass()
obj.instance_method()  # 输出: Called instance_method of <__main__.MyClass object at 0x...>

特点：
需要创建类的实例才能调用。
可以访问实例属性和类属性。

类方法（@classmethod）
在 Python 中，类方法（使用 @classmethod 装饰的方法）可以通过 cls 参数访问和修改类属性
同时也可以调用类中的其他类方法或静态方法
然而，直接调用实例方法是不行的，因为类方法没有绑定到任何实例（即没有 self 参数）
因此无法直接访问实例级别的属性或方法
定义：
类方法是绑定到类本身，而不是实例。
使用装饰器 @classmethod 定义。
第一个参数通常是 cls，表示当前类。
可以通过类或实例调用。
class MyClass:
    class_variable = "I am a class variable"

    @classmethod
    def class_method(cls):
        print(f"Called class_method of {cls}")
        print(f"Accessing class variable: {cls.class_variable}")

# 通过类调用
MyClass.class_method()
# 输出:
# Called class_method of <class '__main__.MyClass'>
# Accessing class variable: I am a class variable

# 通过实例调用
obj = MyClass()
obj.class_method()
# 输出相同

特点：
不依赖于实例，可以直接通过类名调用。
主要用于操作类级别的属性或方法，例如工厂方法、类变量管理等。
实际使用场景
工厂方法-根据不同的输入返回不同的类实例
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_birth_year(cls, name, birth_year):
        current_year = 2023
        age = current_year - birth_year
        return cls(name, age)

person = Person.from_birth_year("Alice", 1990)
print(person.name, person.age)  # 输出: Alice 33

继承支持 子类可以重用父类的类方法
class Parent:
    @classmethod
    def who_am_i(cls):
        print(f"I am {cls.__name__}")

class Child(Parent):
    pass

Parent.who_am_i()  # 输出: I am Parent
Child.who_am_i()   # 输出: I am Child



注意事项
不能直接调用实例方法：由于类方法不接收 self 参数，因此无法直接调用实例方法。如果需要调用实例方法，则需要先创建一个类的实例。 
如果尝试从类方法中调用实例方法，将会导致错误，因为实例方法需要 self 参数来指代具体的实例对象。
访问实例属性：同样地，类方法也不能直接访问实例属性，因为它们没有 self 参数指向具体的实例。要访问实例属性，必须通过实例对象进行访问。
如何从类方法中调用实例方法？
如果你确实需要从类方法中调用实例方法，可以通过先创建一个实例来实现：

Python
深色版本
class MyClass:
    @classmethod
    def class_method_with_instance(cls):
        # 创建实例
        instance = cls()
        # 调用实例方法
        instance.instance_method()

    def instance_method(self):
        print("This is an instance method called from a class method.")

# 调用类方法，通过在类方法中实例化产生实例对象后，后调用实例方法
MyClass.class_method_with_instance()




静态方法（@staticmethod）
定义：
静态方法既不绑定到类，也不绑定到实例。
使用装饰器 @staticmethod 定义。
不需要 self 或 cls 参数。
可以通过类或实例调用。

class MyClass:
    @staticmethod
    def static_method():
        print("Called static_method")

# 通过类调用
MyClass.static_method()  # 输出: Called static_method

# 通过实例调用
obj = MyClass()
obj.static_method()  # 输出: Called static_method
特点：
不依赖于类或实例的状态。
主要用于组织工具函数或与类相关的辅助功能。

实际应用场景
工具函数提供一些与类相关但不需要访问实例或类状态的功能。
class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def multiply(a, b):
        return a * b

result = MathUtils.add(5, 3)
print(result)  # 输出: 8


封装逻辑--将某些逻辑封装在类中，但这些逻辑与实例无关
class FileHandler:
    @staticmethod
    def read_file(file_path):
        with open(file_path, 'r') as file:
            return file.read()

    @staticmethod
    def write_file(file_path, content):
        with open(file_path, 'w') as file:
            file.write(content)

content = FileHandler.read_file("example.txt")
FileHandler.write_file("output.txt", content.upper())

实例方法：操作实例的属性和行为，必须通过实例调用。
类方法：操作类级别的属性或方法，可以通过类或实例调用，适合实现工厂方法或管理类变量。
静态方法：与类或实例无关的工具函数，可以通过类或实例调用，适合封装辅助逻辑。


------


4.python如何创建文件/读文件/写文件，如果有一个大文件，你一般会怎么处理？这个专题
目的就是为了理解并掌握Python读写文件的知识
***open/read/write的用法，比如read() readline()
a.创建文件
Python中使用内置的open()函数创建文件，若文件不存在，则open()会自动创建一个新文件
# 使用 w 模式（写模式） 创建文件，创建文件的目录就是当前你执行这个脚本所在的目录
file = open("example.txt", "w")
file.close() # 使用open方法打开文件必须手动关闭文件

w 写模式，如果文件存在则覆盖内容，如果文件不存在，则创建新文件
a 追加模式 如果文件不存在，则创建新文件，如果文件存在
x 独占模式，若文件已存在则报错，若文件不存在则创建新文件

写入文件使用
write() writelines() 方法向文件写入内容

# 写入单行内容
with open("example.txt", "w") as file:
    file.write("Hello, Python!\n")
    file.write("This is a new line.\n")

# 写入多行内容
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
with open("example.txt", "w") as file:
    file.writelines(lines)

# # 使用 追加模式新建文件，若文件已有，文件内容不会被覆盖
# file = open("test.txt", "a")
# file.close()

# # 使用 覆盖写模式新建文件，若文件已有，文件内容被覆盖
file = open("tests.txt", "w")
file.close()

# 覆盖写，写入多行,然后再追加写入单行
# lines = ["nihao\n", "wo hen hao \n"]
# with open("example.txt", "w") as file:
#     file.writelines(lines)
#
# with open("example.txt", "a") as file:
#     file.write("wo zhen de hen hao")

注意-读取文件，既打开文件选择 r 模式 是不能创建新文件的
读取文件使用 read() readline() readlines() 方法从文件中读取内容

read()方法-读取整个文件的内容
# 读取整个文件的内容
# example.txt 文本内容
# nihao
# wo hen hao 
# wo zhen de hen hao
with open("example.txt", "r") as file:
    content = file.read()
    print(content)

print("------")
print(content, type(content))
print(repr(content))    # 读取文件原始内容'nihao\nwo hen hao \nwo zhen de hen hao'


readline() 读取一行内容，可以写一个while循环来读取全部内容
# 读取文件一行的内容
with open("example.txt", "r") as file:
    content = file.readline()
    print(content)
    content = file.readline()
    print(content)
    content = file.readline()
    print(content)
    content = file.readline()
    print(1111)
    print(content)  # 打印空行，读不到东西时
    print(1111)

#使用 readline() 方法读取全部文件,通过一个while循环来读取即可
with open("example.txt", "r") as file:
    while True:
        content = file.readline()
        if not content:
            break
        print(content)
        # 可以使用strip()方法，去掉字符串前后空格和转义字符

# 使用readlines() 读取文件全部,返回一个列表，列表中每个元素是每行的数据，包含转义字符
with open("example.txt", "r") as file:
    content = file.readlines()
    print(content)

print(content)


# 使用这种方法也可以逐行读取文件内容，实际上就是迭代器的原理
with open("example.txt", "r") as file:
    for f in file:
        print(f)

# 写一个生成器来读取大文件
def read_big(path):
    with open(path,"r") as file:
        while True:
            content = file.readline().strip()
            if not content:
                break
            yield content
# 生成器第二种写法
def read_big(path):
    with open(path,"r") as file:
        for line in file:
            yield line


aa = []
for a in read_big("example.txt"):
    aa.append(a)
print(aa)

使用Python如何读取大文件
1.首先我们常见的使用read() readlines() 肯定是不可以的，因为一次性奖文件读完存在内存中，导致内存占用过高，为了高效的处理大文件
我们使用逐行读取或者分块读取的模式

a.逐行读取，我们使用如下
with open("example.txt", "r") as file:
    for f in file:
        print(f) 
        
b.分块读取
with open("example.txt", "r") as file:
    content = file.read(1024)

# 使用read() 方法 分块读取
a = []
with open("example.txt", "r") as file:
    while True:
        content = file.read(1) # 每次仅读取1个字符
        if not content:
            break
        a.append(content)

print(a)


------


5.Python os模块的用法
os.path 及 pathlib 是Python中用于处理文件路径和目录的2个模块

os.path是一个基于字符串操作的模块，适合处理简单路径操作
a.检查文件或目录是否存在 
import os

if os.path.exists("example.txt"): # 检查的是当前文件的执行路径下是否有这个文件，如果存在返回True 不存在 返回False
    print(1)
else:
    print(2)
    
  
b.获取文件的绝对路径
absolute_path = os.path.abspath("example.txt")
print(absolute)

c.获取文件名，不包含路径
file_name = os.path.basename("/a/b/example.txt")
print(file_name) # 输出  example.txt

d.获取文件所在目录
direct = os.path.dirname("/a/b/example.txt")
print(direct) # 输出/a/b

e.连接路径
new_path = os.path.join("/1/2", "test.txt") 里边可以有多个选项，但是如果出现绝对路径 如d:/  则这个路径之前的都会被忽略，会从绝对路径开始往后连接
print(new_path) # 输出/1/2/test.txt   多个绝对路径会使用最后一个路径

f.获取当前工作目录
current_dir = os.getcwd()
print(current_dir)

e.更改当前工作目录
os.chdir("") # 切换到指定的目录，方便后续操作该目录下的文件

f.创建目录
注意当前文件夹已经存在时，则不能创建
os.mkdir("new") 创建单级目录，如果你写成new/old_1,那你的目的是想在当前目录下的new文件夹下创建一个old_1文件夹，如果父级不存在，则会报错
os.makedirs("new/old") 创建多级目录 会递归创建

e.删除目录
os.rmdir("") 删除空目录
删除非空目录
import shutil
shutil.rmtree("")

f.列出目录内容
files = os.listdir(".")  支持绝对路径和相对路径
print(files) 列出当前目录下所有文件和子目录--返回的是一个列表

e.检查文件或目录是否存在,支持绝对路径或者相对路径 就看最后的在不在，在就是true  不在就是false
if os.path.exists("text.txt")

f.检查是文件还是目录
os.path.isfile("") 是文件 返回True
os.path.isdir("") 是目录 返回True

e.删除文件
os.remove("example.txt")

f.重命名文件或目录
os.rename("old.txt", "new.txt")

e.运行系统命令
os.system()  返回值通常是状态码

f.拆分路径
path = "/path/to/example.txt"
拆分为目录和文件名
direct, file_name = os.path.split(path)
print(direct)   输出 /path/to
print(file_name) 输出example.txt

拆分为文件名和扩展名
root, ext = os.path.splitext(file_name)
print(root) 输出 example
print(ext) 输出.txt


------


6.Python日志模块的用法


------


7.Python如何调试代码，断点怎么用


------


8.Python的魔法方法
举例-如构造与初始化使用的，用于初始化对象，触发时机是在创建实例时自动调用
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(p.name, p.age)  # 输出: Alice 30

------


9.迭代器的原理
在 Python 中，迭代器（Iterator） 是一种可以逐个访问集合（如列表、元组、字典等）中元素的对象。它遵循迭代器协议，即实现了两个方法：
__iter__()：返回迭代器对象本身。
__next__()：返回集合中的下一个元素；如果集合中没有更多元素，则抛出 StopIteration 异常。
迭代器的核心特点是惰性求值（Lazy Evaluation），即只有在需要时才会生成下一个值，而不是一次性生成所有值。这使得迭代器非常适合处理大数据集或无限序列。

迭代器的工作原理
当你使用 for 循环遍历一个可迭代对象时，Python 会自动调用 iter() 方法将该对象转换为迭代器。
然后，for 循环通过不断调用迭代器的 __next__() 方法获取下一个元素，直到遇到 StopIteration 异常为止。

与可迭代对象的区别
可迭代对象（Iterable）：任何可以被迭代的对象，比如列表、元组、字符串、字典等。它们可以通过内置函数 iter() 转换为迭代器。
迭代器（Iterator）：是一个具体的迭代工具，用于逐个访问可迭代对象的元素。

实际的测试开发工作中如处理大数据集
# 示例：逐行读取大文件
def read_large_file(file_path):
    with open(file_path, "r") as file:
        for line in file:  # 文件对象是可迭代的---for循环让一个对象变成了一个可迭代的对象，for循环会先调用内置方法iter()，然后逐步的调用
                           # next()方法，直到出现异常后，捕获并结束
            yield line.strip()

# 使用迭代器逐行处理文件
for line in read_large_file("large_file.txt"):
    print(line)

# 上述代码就是我们学习Python读取大文件时推荐的方法，写一个生成器来实现大文件的惰性取值
# 而生成器本身默认遵守迭代器协议，所以，可以使用for循环来遍历可迭代对象，读取大文本内所有的值
# 生成器隐式的遵循迭代器协议，生成器是一种特殊的迭代器
# for 循环的本质就是在用迭代器来取 可迭代对象的值
# 我们来解释上述代码这个for循环为什么能循环这个方法，且读取大文件中的全部数据？
# 首先
生成器函数
使用 yield 的函数被称为生成器函数。
当你调用一个生成器函数时，它不会立即执行函数体中的代码，而是返回一个生成器对象。这个生成器对象是一个迭代器
这个生成器对象是一个特殊的迭代器，遵循 Python 的迭代器协议。
迭代器协议的核心方法：
__iter__()：返回迭代器本身（即生成器对象）。
__next__()：执行生成器函数的逻辑，直到遇到下一个 yield 表达式，返回 yield 后的值；如果没有更多的 yield，则抛出 StopIteration 异常。
换句话说，生成器函数的逻辑实际上被封装在了生成器对象的 __next__() 方法中。

迭代器协议
在 Python 中，任何实现了 __iter__() 和 __next__() 方法的对象都是迭代器。
for 循环会自动调用对象的 __iter__() 方法获取迭代器，然后不断调用 __next__() 方法获取下一个值，直到遇到 StopIteration 异常为止。

首先定义生成器函数：
这是一个生成器函数，使用了 yield。
每次调用 yield 时，函数会暂停执行，并返回当前行（经过 strip() 处理后的结果）。
下一次调用时，函数从上次暂停的地方继续执行。

调用生成器函数
第一步：read_large_file("large_file.txt")
调用 read_large_file 函数并不会立即执行函数体中的代码。
相反，它返回一个生成器对象（假设为 gen），这个对象是一个迭代器。
第二步：for 循环
for 循环会自动调用生成器对象的 __iter__() 方法，获取迭代器（在这个例子中，生成器对象本身就是迭代器）。
然后，for 循环会不断调用生成器对象的 __next__() 方法，获取下一个值。
第三步：逐行读取文件
每次调用 gen.__next__() 时，生成器函数会继续执行到下一次 yield。*****非常重要，就相当于生成器
函数就这么规定的，你掉的时候就相当于要执行这个函数对应的逻辑，要执行到yield处，返回值，并停止在这里
文件对象 file 是一个可迭代对象，for line in file 会逐行读取文件内容。
每次读取一行后，yield line.strip() 返回当前行（去掉首尾空白字符）。
当文件读取完毕时，for line in file 结束，生成器函数也会结束，并抛出 StopIteration 异常。
for 循环捕获 StopIteration 异常后，自动退出循环。

为什么能逐行读取整个大文件？
惰性求值：
生成器函数不会一次性将整个文件加载到内存中，而是逐行读取并返回。
每次只处理一行数据，因此即使文件非常大，也不会占用大量内存。
文件对象的迭代特性：
在 Python 中，文件对象是可迭代的，for line in file 会逐行读取文件内容。
生成器函数利用了这一特性，每次从文件中读取一行，并通过 yield 返回。
生成器的状态保持：
生成器函数在每次 yield 后会暂停执行，保存当前的状态（例如文件指针的位置）。
下一次调用时，从上次暂停的地方继续执行，继续读取下一行。

生成器函数的逻辑实际上被封装在了生成器对象的 __next__() 方法中。每次调用 __next__() 时：
生成器函数从上一次暂停的地方继续执行。
遇到 yield 时，返回 yield 后的值，并暂停执行。
如果函数执行完毕（没有更多的 yield），则抛出 StopIteration 异常。

可以通过手动调用gen.__next__() 方法来验证

生成器与迭代器的关系：
生成器对象本质上是一个迭代器，它的 __next__() 方法实现了生成器函数的逻辑。
for 循环会自动调用生成器对象的 __next__() 方法，逐个获取值，直到抛出 StopIteration。



生成无限序列-使用迭代器，不会占用大量内存
# 示例：生成无限递增整数
class InfiniteCounter:
    def __init__(self):
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.count += 1
        return self.count

counter = InfiniteCounter()
for _ in range(5):  # 只取前 5 个值
    print(next(counter))
我们来理解下这段代码为什么会逐个打印 1 2 3 4 5
在普通的函数调用中，如果return完成后，这个函数的局部变量会被销毁，下次再调用时，相当于新的开始，不会在之前的基础上运行
def counter():
    count = 0
    count += 1
    return count

print(counter())  # 输出: 1
print(counter())  # 输出: 1

上述代码核心是实现了迭代器协议的一个类，在Python中一个对象如果想要成为迭代器，必须实现以下2个方法
__iter__() 返回迭代器本身
__next__() 返回下一个值，无值时抛出stopiteration 异常
上述类实现了这2个方法因此它可以实例化对象后，使得对象成为迭代器对象
迭代器对象是一个类的实例，它的属性值 如self.count = 0 保存在实例变量中，你可以认为这个值是绑定在实例本身的
每次调用next(counter) ,__next__()方法仅仅是更新了实例变量，并没有显示的重新初始化它
初始化创建对象，self.count = 0 
首次调用 next(counter) 触发__next()__方法 返回结果1，self.count是实例变量，属于counter对象
不是局部变量，所以__next__()方法执行完成后，self.count的值是保留在counter对象中的
这个值是类的实例的变量，生命周期同counter对象绑定，只要对象存在，那值就存在，且可以在多次调用之间共享
不会像普通函数的局部变量一样，在函数执行完成后销毁

其实上面的描述和迭代器本身无关，重点还是这个变量是一个实例变量，生命周期绑定在实例对象上
class Counter:
    def __init__(self):
        self.count = 0  # 初始化计数器为0

    def increment(self):
        self.count += 1  # 每次调用increment方法时增加计数器
        return self.count

# 创建Counter对象
counter = Counter()

print(counter.increment())  # 输出: 1
print(counter.increment())  # 输出: 2
print(counter.increment())  # 输出: 3
在这个例子中：
self.count 是 Counter 类的一个实例属性。
每次调用 increment() 方法时，它都会对该实例属性进行修改（即增加计数）。
因此，每次调用 increment() 方法时，self.count 的值都是上次调用后的结果
实例属性：实例属性是特定于某个对象实例的变量。它们存储在对象的 __dict__ 属性中，因此每个对象都有自己独立的一份实例属性副本。
方法内的操作：当你在一个方法内部对实例属性进行修改时，这些修改会直接影响该对象的实例属性。因为这些属性是对象状态的一部分，所以它们的状态变化会被保留下来，直到对象被销毁或显式地重新设置。
方法调用之间的状态保持：由于实例属性是持久化的（除非你显式地重置它们），因此不同方法调用之间可以共享和依赖这些属性的状态。


再来一个比较复杂的例子-针对实例变量在多次调用之间的共享
class DataProcessor:
    def __init__(self):
        self.data = []  # 初始化一个空列表作为数据存储

    def add_data(self, value):
        self.data.append(value)  # 向数据列表中添加新值
        print(f"Added {value}, current data: {self.data}")

    def get_data(self):
        return self.data  # 返回当前的数据列表

# 创建DataProcessor对象
processor = DataProcessor()

processor.add_data(10)  # 输出: Added 10, current data: [10]
processor.add_data(20)  # 输出: Added 20, current data: [10, 20]
processor.add_data(30)  # 输出: Added 30, current data: [10, 20, 30]

print(processor.get_data())  # 输出: [10, 20, 30]



# 示例：实现一个斐波那契数列迭代器
class Fibonacci:
    def __init__(self, max):
        self.a, self.b = 0, 1
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.a > self.max:
            raise StopIteration
        current = self.a
        self.a, self.b = self.b, self.a + self.b
        return current

fib = Fibonacci(10)
for num in fib:
    print(num)  # 输出: 0 1 1 2 3 5 8

如何判断一个对象是否是迭代器？
可以使用 isinstance() 和 collections.abc.Iterator 来判断。

from collections.abc import Iterator

my_list = [1, 2, 3]
my_iterator = iter(my_list)

print(isinstance(my_list, Iterator))      # False
print(isinstance(my_iterator, Iterator))  # True

如何手动触发迭代器
from collections.abc import Iterator


my_list = [1, 2, 3]
my_iterator = iter(my_list)

print(next(my_iterator))  # 输出: 1
print(next(my_iterator))  # 输出: 2
print(next(my_iterator))  # 输出: 3

如何处理迭代结束的情况？
当迭代器耗尽时，next() 会抛出 StopIteration 异常。可以通过异常捕获机制处理
my_list = [1, 2, 3]
my_iterator = iter(my_list)

while True:
    try:
        print(next(my_iterator))
    except StopIteration:
        print("Iteration complete.")
        break
        
        
惰性求值生成斐波那契无限序列
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
for _ in range(10):  # 只取前 10 个斐波那契数
    print(next(fib))
    



什么是惰性求值
急切求值（Eager Evaluation）：大多数编程语言默认采用的方式，在定义表达式的同时立即计算其值。例如，在 Python 中，当你创建一个列表推导式时，所有元素都会立刻被计算出来
squares = [x*x for x in range(10)]

惰性求值：只有当访问到具体的元素时，才进行相应的计算。Python 中的生成器就是惰性求值的一个例子。
squares = (x*x for x in range(10))
print(next(squares))  # 输出: 0


yield关键字知识
yield 是 Python 中实现生成器（Generator） 的关键字，它的作用是让函数暂停执行，并在下一次调用时从暂停的地方继续执行。具体来说：

yield 的工作原理：
当函数中遇到 yield 时，函数会暂停执行，并将 yield 后面的值返回给调用者。
函数的状态（包括局部变量、指令指针等）会被保存。
下一次调用生成器的 __next__() 方法（或通过 next() 函数）时，函数会从上次暂停的地方继续执行。
与 return 的区别：
return 会直接结束函数，并返回一个值。
yield 不会结束函数，而是暂停函数，并在需要时恢复执行。

yield运行例子
def simple_generator():
    print("Start")
    yield 1
    print("After first yield")
    yield 2
    print("After second yield")
    yield 3
    print("End")

# 创建生成器对象
gen = simple_generator()

# 第一次调用 next()
print(next(gen))  # 输出: Start \n 1

# 第二次调用 next()
print(next(gen))  # 输出: After first yield \n 2

# 第三次调用 next()
print(next(gen))  # 输出: After second yield \n 3

# 第四次调用 next()（没有更多的 yield）
try:
    print(next(gen))
except StopIteration:
    print("Generator exhausted")  # 输出: End \n Generator exhausted
    
    
    
    
调用生成器函数
def my_generator():
    print("Start")
    yield 1
    print("After first yield")
    yield 2
    print("End")

gen = my_generator()
调用 my_generator() 不会执行函数体中的代码。
它只是返回一个生成器对象 gen，这个对象是一个迭代器。

使用for循环遍历
for value in gen:
    print(value)
    
第一次调用 gen.__next__()
for 循环调用 gen.__next__()。
生成器函数开始执行：
打印 "Start"。
遇到第一个 yield，返回值 1，并暂停执行。
输出：
start 
1
"""
"""
字符串find方法，找到匹配的第一个，返回下标，找不到返回-1
字符串是不可变的数据类型
.replace(旧的，新的)，返回一个改变后的字符串，原始字符串不会被改变
upper() 大写
lower() 小写
strip() 处理字符串开头结尾的空格，不能处理中间的
.split() 分割字符串，成为一个列表,默认用空格，如果里边写空字符串会报错，可以写为" "等同于空格
还可以连接列表为字符串 "需要的字符串".join(list),列表中元素必须为字符串，因为你要用字符串拆分为列表，字符串列表再合并为整个字符串
str.count("") 有就返回统计的个数，没有就返回0 计数
str.startswith() 判断字符串用什么开头，返回布尔值
str.endswith() 判断字符串结尾 返回布尔值
字符串支持
isalpha() 若字符串中的所有字符都是字母，并且至少有一个字符，返回True
isdigit() 若字符串中所有字符串都是数字，并且至少有一个字符，返回True
isalnum() 字符都是字母或者数字，并且至少有一个，返回True

列表是可变 有序 可重复，支持索引和切片的
list 是一种非常灵活且常用的数据结构，用于存储一系列有序的元素，列表中的元素可以是
不同类型的数据，比如说整数，浮点数，字符串，列表，字典，元组等Python支持的数据类型
创建列表的简单的方式是就是直接使用[] 或者list() 可以创建空列表
也可以直接使用list()构造函数从可迭代对象来创建列表，比如说list("abd")  list((1,2,3))
得到的输出为["a", "b", "d"]   [1,2,3]
通过切片来访问元素
从0开始，区间切片是左闭右开，-1等于从尾巴开始

由于列表是可变数据类型，可以直接通过索引来修改元素,但是注意索引范围必须是当前列表的范围之内
使用list.append() 在列表的末尾添加一个元素，这是一个操作，改原列表
insert(索引，值) 在指定位置添加，这个位置本身的元素靠后,如果不是范围内的，则排到最后，这是一个操作，改原列表
list.extend(iterable) 扩展另一个可迭代对象,这是一个操作，无返回值，针对list本身修改

使用remove() 删除列表中首次出现的值,如果没有值，会报错
pop() 移除并返回指定位置的元素，默认移除最后一个元素,按索引可以删除，并返回这个元素
超范围的会报错,删除到最后空列表使用会报错
del关键字  可以删除列表中的某个切片或者删除整个列表 del list[索引]

可以使用切片来获取列表的一部分，也可以使用切片来修改多个元素
num[1:4] = [1,2,3]  就是将num列表中索引为1 2 3 的原值修改为 新值 1 2 3
list[0] = 新值，将这个位置原来的值更新为新值


如果同时要获取元素及索引 使用enumerate()
for index, fruit in enumerate(list):
    print(index, fruit)


list.index("值") 返回第一个等于x的元素的索引

sort()对列表进行原地排序
list.sort() 默认从小到大排序
aa = sorted(list) 返回一个已被排序的新列表，不改变原来的列表
len(list) 获取列表的长度，有多少个值
list.count(值)统计元素在列表中出现的次数
list.reverse() 反转列表中元素的顺序，原地翻转
list(reversed(list)) 新列表翻转，原列表不变
list.clear() 清空列表
sum(list) 仅限数字列表
高级操作-列表推导式
list_1 = [x**2 for x in range[10]]

map()函数 应用函数至列表所有的元素
filter() 根据条件筛选列表元素
aa = list(map(lambda x: x**2, list_1))
bb = list(filter(lambda x: x%2 == 0, list_1))

复制列表，直接赋值不会创建列表的副本，而是2个变量指向同一个列表，如果要创建副本，使用切片或者copy()方法



在Python中元组是一种基础且常用的数据结构，也是Python的一种数据类型，它与列表类似，都是用来存储多个元素的序列
和列表的一个关键区别就是元组是不可变的，而列表是可变的
但是如果元组内我们放一个列表，我们可以切片得到这个列表，并对列表内的元素进行修改，这样看起来元组又是可变的
只不过是我们间接改变，但是对于元组本身来说，这里还是一个列表

我们通常使用一个()来定义一个元组
如这种l = () 这是一个空元组
一般我们可以省略括号如这样 l = 1,2,3 这其实也是一个元组
对于单元素的元组，我们使用这种表示 l = (5,)
l = (5) 不是一个元组，而是一个整数

也可以将元组转变为列表  列表转换为元组
如tuple([1,2,3]) 输出(1,2,3)


同列表一致，我们可以通过索引来访问元组内的元素，支持切片操作，但是不支持修改
因为元组是不可变的数据类型
支持遍历
支持拼接和重复，不会改变原有，而是生成新的元组
l1 = (1,2)
l2 = (3,4)
l3 = l1 + l2  输出  (1,2,3,4)
l4 = l1*2 输出 (1,2,1,2)

使用in 来判断元素是否在元组  列表  字符串内  返回布尔值

可以获取元组的长度  最值
len(tuple)
max(tuple)
min(tuple) 

元组的解包和打包
打包
t = 1,2,3
print(t)   (1,2,3)

x,y,z = t
print(x)
print(y)
print(z)  
一次打印 1 2 3,但是注意数量一定要匹配，否则会报错
或者可以用*来捕获剩余的
a, *b = t
print(a) 输出1
print(b) 输出[2,3] 注意这是一个列表

元组不可变，所以数据不会被意外修改，一般用来存储不变的数据，如配置项，常量，日期，时间，坐标等
其也可以作为字典的键，列表不能作为字典的键，但是元组可以
函数返回多个值时，实际上返回的是一个元组
元组的不可变 和高效（和列表来对比，元组更快）

python中集合用来存储不重复的元素，与数学中集合的概念非常类似
集合是一种无序，不重复元素的容器
集合无序，所以不能通过索引访问
集合内的元素具有唯一性，不允许有重复的元素
集合是可变数据类型，但是集合中的元素必须是不可变类型的数据，如整数，字符串，元组
集合允许添加或删除元素，但是不能修改已有元素，这应该是因为集合内部是无序的
使用{1,2,3} 或者 a = set() 创建空集合
{} 不是空集合，表示字典
集合常用操作
set.add(1)
set.remove(2) ,若不存在该元素则会报错 KeyError
set.discard(2) , 安全删除，不存在的元素不会报错
set.clear() 清空集合
len(set) 集合长度

集合的运算
| 并,大家一起有的-全集
& 交,2个一起有的
A-B 差,前者单独有的，仅属于A但是不属于B
^ 2个人各自单独有的，组成新的集合

集合关系判断
set.issubset(b) 是否为子集  判断set是不是b集合的子集 返回布尔值，满足返回True
set.issuperset(b) 是否为父集  判断set是不是b集合的父集 返回布尔值，满足返回True
set.isdisjoint(b) 是否没有交集  返回布尔值，无交集返回True

如果你希望你的集合是不可变的，比如说作为字典的键，使用frozenset(set)

集合的常见应用场景未，快速去重列表中的重复项，统计数据集之间的差异，交集等，集合的查找速度比列表快


字典是一种可变，无序的，key/value组成每一个元素
键唯一，若重复，后面的会覆盖前面的
字典可以增删改
使用{}来创建空字典 或者直接a = dict() 也可以创建空字典
可以使用元组列表构造 dict([("name","alice")]) 

取值通过 a[key] = value 通过键来获取值，如果键不存在，则会抛出KeyError的错误
如果想安全获取，使用dict.get()，获取不到输出None，也可以指定默认的值，dict.get("value", "指定获取不到时返回的值")
添加或修改键值对
a[新键] = 新值 字典的添加
a[原来存在的键] = 新值 字典的修改

删除键值对
del d[键]

aa = dict.pop(键) 删除后返回对应的值

dict.clear() 清空字典


遍历字典

for key in dict:
    print(key)  遍历键


for value in dict.values():
    print(value) 遍历值


for a,b in dict.items():
    print(a,b) 遍历键值对


判断键是否存在
if a in dict:


合并2个字典
d1.update(d2) 先展示d1的，然后d2，若有重复的，后面的覆盖前面的

字典的合法键必须是不可变数据类型的 如整数 浮点数， 布尔值 字符串 元组(要求元组内的元素均为不可变数据类型)
冻结集合

键不能是 可变数据类型，如列表 字典 集合
值没有要求，是Python支持的数据类型即可

dict.items() 返回一个视图对象 view object 包含了字典中所有的键值对，每个元素是一个元组
如果要打印返回的type 则是 dict_items
支持遍历，可以同时拿到键和值

还有dict.keys() dict.values() 这2种分别仅获取字典的键 或者字典的值



"""


def nums():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b


# 正常写法，可以打印10个斐波那契数
gen = nums()
for i in range(10):
    print(next(gen))

# 上述代码符合预期的原因：
# 在这个版本中，nums() 被调用一次，并返回一个生成器对象 gen。
# for 循环中通过 next(gen) 每次从同一个生成器对象中获取下一个值。
# 所以请注意 你掉生成器函数，会生成一个对象，你的for循环是对这个对象的


# 错误写法，导致全部打印0
# for i in range(10):
#     print(next(nums()))

# 原因
# 在这个版本中，每次循环都会重新调用 nums()，从而创建一个新的生成器对象。
# 每次调用 next(nums()) 时，都会从新生成器的初始状态开始执行

# 为什么一直打印0，因为生成器对象的状态是独立的
# 每次调用 nums() 都会返回一个全新的生成器对象。
# 每个生成器对象都有自己的独立状态（即 a 和 b 的初始值）。
# 当你调用 next(nums()) 时：
# 创建了一个新的生成器对象。
# 调用 next() 获取该生成器的第一个值（即 a = 0）。
# 然后这个生成器对象被丢弃，下一次循环又会创建一个新的生成器对象。
# 所以每次调用next(nums()) 都会让生成器从初始状态开始执行
#
# 所以调用生成器函数一定要注意，调用后会返回一个生成器对象，每次调用，生成器对象的状态是独立的
# 你要确保你的操作是对同一个生成器对象操作
# 如果你把这个nums()直接写在循环内，那看起来像每次循环时都在调用这个方法，而每次调用就会生成一个独立的生成器对象
# 所以这里需要注意，和调用普通方法不一样的是，调用生成器函数是会先返回一个对象


# 如下写法，第一种和上述正确写法一样，你的next方法需要保证对同一个生成器对象操作
# 第二种写法，你直接对这个生成器对象进行for循环,但是注意函数内是否有死循环
# 归根到底，还是对同一个生成器对象操作，即可保证你的预期

# gen = nums()  # 只调用一次，创建生成器对象
#
# for i in range(10):
#     print(next(gen))  # 从同一个生成器对象中获取下一个值


# for value in nums():
#     print(value)
#     if value > 30:  # 设置终止条件，避免无限循环
#         break


# 生成器的核心特点：生成器对象是有状态的，需要持续使用同一个生成器对象才能逐步生成值。
